#region File Description
//-----------------------------------------------------------------------------
// GameplayScreen.cs
//
// Microsoft XNA Community Game Platform
// Copyright (C) Microsoft Corporation. All rights reserved.
//-----------------------------------------------------------------------------
#endregion

#region Using Statements
using System;
using System.Linq;
using System.Threading;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using System.Collections.Generic;

using Engine.Cameras;
using Engine.Graphics;
using Engine.GameStateManagement;
using Engine.Input;
using Estadio.GameObjects;
using Engine;
using Engine.Helpers;
using Microsoft.Xna.Framework.Media;
using Engine.Physics;
using Engine.Physics.PObjects;
#endregion

namespace Estadio
{
    /// <summary>
    /// This screen implements the actual game logic. It is just a
    /// placeholder to get the idea across: you'll probably want to
    /// put some more interesting gameplay in here!
    /// </summary>
    class GameplayScreen : GameScreen
    {
        #region Fields

        ContentManager content;
        SpriteFont gameFont;

        GraphicsDevice device;

        Vector2 playerPosition = new Vector2(100, 100);
        Vector2 enemyPosition = new Vector2(100, 100);

        Random random = new Random();

        float pauseAlpha;

        Camera camera;
        //Model table;
        //Matrix[] tableAbsoluteBoneTransforms;

        List<Entity> entities = new List<Entity>();

        #endregion

        #region Initialization


        /// <summary>
        /// Constructor.
        /// </summary>
        public GameplayScreen()
        {
            TransitionOnTime = TimeSpan.FromSeconds(1.5);
            TransitionOffTime = TimeSpan.FromSeconds(0.5);

        }


        /// <summary>
        /// Load graphics content for the game.
        /// </summary>
        public override void LoadContent()
        {
            if (content == null)
                content = new ContentManager(ScreenManager.Game.Services, "Content");

            device = ScreenManager.GraphicsDevice;
            float aspect = device.Viewport.AspectRatio;

            ArcCamera cameraArc = new ArcCamera(MathHelper.PiOver4, aspect, 0.1f, 100.0f, -30.0f, 225, 3.5f);
            PerspectiveCamera cameraPersp = new PerspectiveCamera(MathHelper.PiOver4, aspect, 0.1f, 100.0f);
            TargetCam cameraTarget = new TargetCam(MathHelper.PiOver4, aspect, 0.1f, 100.0f);

            Camera.ActiveCamera = cameraArc;
            Camera.ActiveCamera.Position = new Vector3(0, 1.0f, 10.0f);
            Camera.ActiveCamera.Target = Vector3.Zero;

            entities.Add(Camera.ActiveCamera);

            //table = content.Load<Model>("Table");
            //tableAbsoluteBoneTransforms = new Matrix[table.Bones.Count];
            //table.CopyAbsoluteBoneTransformsTo(tableAbsoluteBoneTransforms);

            gameFont = content.Load<SpriteFont>("gamefont");

            entities.Add(new MeshEntity(null, content.Load<Model>("sphere").Meshes[0], Matrix.Identity));
            entities.Last().LocalWorld = Matrix.CreateScale(0.05f);

            Model tableModel = content.Load<Model>("Table");
            foreach (ModelMesh mesh in tableModel.Meshes)
            {
                foreach (BasicEffect effect in mesh.Effects)
                {
                    effect.EnableDefaultLighting();
                    effect.DirectionalLight0.Enabled = true;
                    effect.DirectionalLight0.Direction = Vector3.Normalize(new Vector3(1, -1, 1));
                    effect.SpecularPower = 8.0f;
                    effect.SpecularColor = Vector3.One;

                }
            }
            //entities.Add(new ModelEntity(tableModel));

            InputManager input = InputManager.Instance;

            var ballModel = content.Load<Model>("sphere");

            #region Arrow Ball

            Ball arrowsBall = new Ball(null, content.Load<Model>("sphere"));
            arrowsBall.AddControlKeys
                (
                () => { return input.KBoard.IsKeyDown(Keys.Left).ToFloat(); },
                () => { return input.KBoard.IsKeyDown(Keys.Right).ToFloat(); },
                () => { return input.KBoard.IsKeyDown(Keys.Up).ToFloat(); },
                () => { return input.KBoard.IsKeyDown(Keys.Down).ToFloat(); }
                );

            //entities.Add(arrowsBall);

            #endregion

            #region WASD Ball
            Ball wasdBall = new Ball(null, content.Load<Model>("sphere"));
            wasdBall.AddControlKeys
                (
                () => { return input.KBoard.IsKeyDown(Keys.A).ToFloat(); },
                () => { return input.KBoard.IsKeyDown(Keys.D).ToFloat(); },
                () => { return input.KBoard.IsKeyDown(Keys.W).ToFloat(); },
                () => { return input.KBoard.IsKeyDown(Keys.S).ToFloat(); }
                );

            //entities.Add(wasdBall);
            #endregion

            #region Video Test
            VideoPanel vid = new VideoPanel(content.Load<Video>("Videos\\Lake"));
            vid.FullScreen = true;
            vid.IsLooping = true;
            //vid.Play();
            //entities.Add(vid);
            #endregion

            //entities.Add(new ModelEntity(content.Load<Model>("footballTest")));
            //entities.Last().Scale = Vector3.One * 5.0f;

            var freeBall = new PhysicsModelEntity(null, ballModel);
            freeBall.Velocity = new Vector3(-1, 0, 1);
            freeBall.Type = CollisionType.Sphere;
            freeBall.LocalWorld = Matrix.CreateScale(0.01f) * Matrix.CreateTranslation(Vector3.UnitY * 1.0f);
            entities.Add(freeBall);

            var footTableModel = content.Load<Model>("footballTest");
            foreach (ModelMesh mesh in footTableModel.Meshes)
            {
                foreach (BasicEffect effect in mesh.Effects)
                {
                    effect.EnableDefaultLighting();
                    effect.DirectionalLight0.Enabled = true;
                    effect.DirectionalLight0.Direction = Vector3.Normalize(Vector3.One);
                    effect.PreferPerPixelLighting = true;
                }
            }
            var footTableEntity = new FootCourt(null, footTableModel);
            footTableEntity.Scale = Vector3.One * 8.0f;
            entities.Add(footTableEntity);

            foreach (var loc in footTableEntity.Locators)
            {
                entities.Add(new FootballPlayer(null, content.Load<Model>("Cylinder"), loc));
            }
            //entities.Last().World = Matrix.CreateScale(0.05f);
            //entities.Last().Eff

            #region Collision / Physics Test
            float width = 10.0f;
            float height = 5.0f;
            float thickness = 0.1f;
            float wallHeight = 1.0f;

            var boxModel = content.Load<Model>("Cats");

            var floor = new PhysicsModelEntity(null, boxModel);
            
            floor.Plane = new Plane(new Vector4(Vector3.UnitY, 0));

            floor.Type = CollisionType.Box;
            floor.LocalWorld = Matrix.CreateScale(width, thickness, height);

            var wall1 = new PhysicsModelEntity(null, boxModel);
            wall1.Type = CollisionType.Box;
            wall1.Plane = new Plane(new Vector4(Vector3.UnitZ, 0));

            var wall2 = new PhysicsModelEntity(null, boxModel);
            wall2.Type = CollisionType.Box;
            wall2.Plane = new Plane(new Vector4(-Vector3.UnitZ, 0));

            var wall3 = new PhysicsModelEntity(null, boxModel);
            wall3.Type = CollisionType.Box;
            wall3.Plane = new Plane(new Vector4(Vector3.UnitX, 0));

            var wall4 = new PhysicsModelEntity(null, boxModel);
            wall4.Type = CollisionType.Box;
            wall4.Plane = new Plane(new Vector4(-Vector3.UnitX, 0));


            wall1.LocalWorld = Matrix.CreateScale(width, wallHeight, thickness) * Matrix.CreateTranslation(-Vector3.UnitZ * (height / 4));
            wall2.LocalWorld = Matrix.CreateScale(width, wallHeight, thickness) * Matrix.CreateTranslation(Vector3.UnitZ * (height / 4));
            wall3.LocalWorld = Matrix.CreateScale(thickness, wallHeight, height) * Matrix.CreateTranslation(-Vector3.UnitX * (height / 2));
            wall4.LocalWorld = Matrix.CreateScale(thickness, wallHeight, height) * Matrix.CreateTranslation(Vector3.UnitX * (height / 2));

            //entities.Add(floor);
            //entities.Add(wall1);
            //entities.Add(wall2);
            //entities.Add(wall3);
            //entities.Add(wall4);

            CollisionSolver cls = CollisionSolver.Instance;
            PhysicsManager pm = PhysicsManager.Instance;
            pm.Gravity = new Vector3(0, -0.05f, 0);
            pm.Enabled = false;

            cls.Enabled = false;
            cls.Subscribe(freeBall);
            cls.Subscribe(floor);
            cls.Subscribe(wall1);
            cls.Subscribe(wall2);
            cls.Subscribe(wall3);
            cls.Subscribe(wall4);

            freeBall.Collision += new EventHandler<CollisionEventArgs>
            (
                (sender, eventArgs) =>
                {

                    var other = eventArgs.Other as ICollisionable;
                    var pe = sender as IPhysicEntity;

                    pe.Velocity = Vector3.Reflect(pe.Velocity, other.Plane.Normal);
                    //other.Position
                }
            );

            freeBall.Colliding += new EventHandler<CollisionEventArgs>
            (
                (sender, eventArgs) =>
                {
                    if (eventArgs.Other == floor)
                    {
                        var pe = sender as IPhysicEntity;
                        var ic = sender as ICollisionable;
                        BoundingSphere bs = ic.Sphere.Transform(ic.World);
                        float totalHeight = bs.Radius + floor.Position.Y + thickness;
                        if (pe.Position.Y < totalHeight)
                        {
                            pe.Velocity = new Vector3(pe.Velocity.X, 0, pe.Velocity.Z);
                            pe.Position = new Vector3(pe.Position.X, totalHeight, pe.Position.Z);
                        }
                    }
                }
            );

            pm.Add(freeBall);

            #endregion

            //col.Subscribe(wasdBall);

            // A real game would probably have more content than this sample, so
            // it would take longer to load. We simulate that by delaying for a
            // while, giving you a chance to admire the beautiful loading screen.
            Thread.Sleep(100);

            // once the load has finished, we use ResetElapsedTime to tell the game's
            // timing mechanism that we have just finished a very long frame, and that
            // it should not try to catch up.
            ScreenManager.Game.ResetElapsedTime();
        }


        /// <summary>
        /// Unload graphics content used by the game.
        /// </summary>
        public override void UnloadContent()
        {
            content.Unload();
        }


        #endregion

        #region Update and Draw


        /// <summary>
        /// Updates the state of the game. This method checks the GameScreen.IsActive
        /// property, so the game will stop updating when the pause menu is active,
        /// or if you tab away to a different application.
        /// </summary>
        public override void Update(GameTime gameTime, bool otherScreenHasFocus,
                                                       bool coveredByOtherScreen)
        {
            base.Update(gameTime, otherScreenHasFocus, false);

            //Console.WriteLine("CollisionCount :" + CollisionSolver.Instance.Collisions.Count.ToString());

            foreach (var entity in entities)
            {
                entity.Update(gameTime);
            }

            // Gradually fade in or out depending on whether we are covered by the pause screen.
            if (coveredByOtherScreen)
                pauseAlpha = Math.Min(pauseAlpha + 1f / 32, 1);
            else
                pauseAlpha = Math.Max(pauseAlpha - 1f / 32, 0);

            if (IsActive)
            {
                // Apply some random jitter to make the enemy move around.
                const float randomization = 10;

                //camera.Update(gameTime);

                enemyPosition.X += (float)(random.NextDouble() - 0.5) * randomization;
                enemyPosition.Y += (float)(random.NextDouble() - 0.5) * randomization;

                // Apply a stabilizing force to stop the enemy moving off the screen.
                Vector2 targetPosition = new Vector2(
                    ScreenManager.GraphicsDevice.Viewport.Width / 2 - gameFont.MeasureString("Insert Gameplay Here").X / 2, 
                    200);

                enemyPosition = Vector2.Lerp(enemyPosition, targetPosition, 0.05f);

                // TODO: this game isn't very fun! You could probably improve
                // it by inserting something more interesting in this space :-)
            }
        }


        /// <summary>
        /// Lets the game respond to player input. Unlike the Update method,
        /// this will only be called when the gameplay screen is active.
        /// </summary>
        public override void HandleInput(InputState input)
        {
            if (input == null)
                throw new ArgumentNullException("input");

            // Look up inputs for the active player profile.
            int playerIndex = (int)ControllingPlayer.Value;

            KeyboardState keyboardState = input.CurrentKeyboardStates[playerIndex];
            GamePadState gamePadState = input.CurrentGamePadStates[playerIndex];

            // The game pauses either if the user presses the pause button, or if
            // they unplug the active gamepad. This requires us to keep track of
            // whether a gamepad was ever plugged in, because we don't want to pause
            // on PC if they are playing with a keyboard and have no gamepad at all!
            bool gamePadDisconnected = !gamePadState.IsConnected &&
                                       input.GamePadWasConnected[playerIndex];

            if (input.IsPauseGame(ControllingPlayer) || gamePadDisconnected)
            {
                ScreenManager.AddScreen(new PauseMenuScreen(), ControllingPlayer);
            }
            else
            {
                // Otherwise move the player position.

                IInputUpdate camUpdate = Camera.ActiveCamera as IInputUpdate;

                if (camUpdate != null)
                    camUpdate.UpdateInputState(input);
            }
        }


        /// <summary>
        /// Draws the gameplay screen.
        /// </summary>
        public override void Draw(GameTime gameTime)
        {
            // This game has a blue background. Why? Because!
            ScreenManager.GraphicsDevice.Clear(ClearOptions.Target,
                                               Color.CornflowerBlue, 0, 0);

            device.BlendState = BlendState.Opaque;
            device.DepthStencilState = DepthStencilState.Default;
            device.RasterizerState = RasterizerState.CullCounterClockwise;



            foreach (Entity entity in entities)
            {
                DrawableEntity de = entity as DrawableEntity;

                if (de != null && de.Visible)
                    de.Draw(gameTime);
            }


            // Our player and enemy are both actually just text strings.
            SpriteBatch spriteBatch = ScreenManager.SpriteBatch;

            //spriteBatch.Begin();

            //spriteBatch.DrawString(gameFont, "// TODO", playerPosition, Color.Green);


            //spriteBatch.End();



            // If the game is transitioning on or off, fade it out to black.
            if (TransitionPosition > 0 || pauseAlpha > 0)
            {
                float alpha = MathHelper.Lerp(1f - TransitionAlpha, 1f, pauseAlpha / 2);

                ScreenManager.FadeBackBufferToBlack(alpha);
            }
        }


        #endregion

    }
}
